/*
 * You should have received a copy of the GNU General Public License
 * along with aKoustiK.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <kore.h>
#include <item.h>
#include <mediaseeker.h>
#include <playlist.h>

#include <KFileItem>
#include <QtCore/QDebug>
#include <QtCore/QDirIterator>
#include <QtCore/QObject>
#include <QtCore/QRunnable>
#include <QtCore/QStringList>
#include <QtCore/QThreadPool>
#include <Phonon/BackendCapabilities>

// meta resolving imports:
#include <QtCore/QFile>
#include <QtCore/QTextCodec>
#include <QtCore/QTime>
#include <taglib/fileref.h>
#include <taglib/tag.h>

/** Instance of this class will be submitted to QThreadPool to run exec() in background thread. */
class SeekRunnable : public QRunnable {
public:
    SeekRunnable(MediaSeeker* mediaSeekerPointer);
    ~SeekRunnable();
    void run();
private:
    MediaSeeker* mediaSeeker;
};

/** Class that handles all metadata resolving and creating of Item from KUrl. Currently taglib is used to resolved metadata. */
class MetaResolver {
public:
    MetaResolver();
    Item* createItem(const KUrl& url);
    bool isSupported(const KUrl& kurl);
private:
    QStringList supportedMimes;
    QTime time;
    QTextCodec* codec;
};

MediaSeeker::MediaSeeker(const QList<QUrl> &targetUrls) : urls(targetUrls) {
    qDebug("MediaSeeker: constructor invoked");
}

MediaSeeker::~MediaSeeker() {
    qDebug("MediaSeeker: destructor invoked");
}

void MediaSeeker::start() {
    if (urls.isEmpty()) {
        return;
    }
    // QThreadPool should delete those runnables himself:
    qDebug() << "MediaSeeker::start() invoked from " << QThread::currentThread();
    QThreadPool::globalInstance()->start(new SeekRunnable(this));
}

void MediaSeeker::exec() {
    qDebug() << "MediaSeeker::exec() invoked from " << QThread::currentThread();
    qDebug("MediaSeeker: seeking for audio files in %i urls...", urls.size());

    MetaResolver metaResolver;
    for (int i = 0; i < urls.size(); i++) {
        const QUrl& url = urls.at(i);
        KUrl kurl(url);
        if (metaResolver.isSupported(kurl)) {
            items.append(metaResolver.createItem(kurl));
            continue;
        }

        QDirIterator dirIterator(url.path(), QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
        while (dirIterator.hasNext()) {
            QString next = dirIterator.next();

            // we do not want to get . and .. in the scanner:
            QString fileName = dirIterator.fileName();
            if (fileName == "." || fileName == "..") {
                continue;
            }

            // we also will skip any playlist files for now:
            {
                /*
              http://en.wikipedia.org/wiki/Playlist:

                There are several types of playlists. Some of the most common are:
                .asx, an XML style playlist containing more information about the items on the playlist.
                .bio, a text-based list of items, with each item on a new line. Each item represents the full path to the file.
                .m3u, a simple text-based list of the locations of the items, with each item on a new line.
                PLC, file extension for AIMP2 media player
                .pls, a text playlist similar to .ini (program settings) files. It normally contains only the location of the items in the playlist.
                .smil is an XML recommendation of the World Wide Web Consortium that includes playlist features. In addition to audio, it supports video and screen layout and is heavily used in Digital Signage.
                Kalliope PlayList (.kpl) is a kind of XML playlist storing developed to speed up loading and managing playlists.
                .pla, Samsung format(?), binary, Winamp handles these
                XSPF, an XML format designed to enable playlist sharing.
                VLC is a format used by VLC Media Player.
                WPL is an XML format used in Microsoft Windows Media Player versions 9–11.
                ZPL is an format used by Zoom Player and Creative Zen Media Players.
                FPL is a format used by Foobar2000.
                */
                if (fileName.endsWith("m3u", Qt::CaseInsensitive)
                    || fileName.endsWith("asx", Qt::CaseInsensitive)
                    || fileName.endsWith("pls", Qt::CaseInsensitive)
                    || fileName.endsWith("xspf", Qt::CaseInsensitive)
                    || fileName.endsWith("vlc", Qt::CaseInsensitive)) {
                    continue;
                }
            }

            KUrl nextKurl(next);
            if (metaResolver.isSupported(nextKurl)) {
                items.append(metaResolver.createItem(nextKurl));
            }
        }

    }

    if (items.isEmpty()) {
        qDebug("MediaSeeker: seach complete, no items found.");
        return;
    }
    qDebug("MediaSeeker: seach complete, found %i items. emiting finish...", items.size());
    Playlist* playlist = Kore::instance().playlist();
    emit beforePlaylistAppend(playlist->size(), items.size());
    playlist->appendItems(items);
    emit afterPlaylistAppend();
    qDebug("MediaSeeker: signal emition done, scheduling self for deletion:");
    deleteLater();
}

SeekRunnable::SeekRunnable(MediaSeeker *mediaSeekerPointer) : mediaSeeker(mediaSeekerPointer) {
    qDebug("SeekRunnable: constructor invoked");
}

SeekRunnable::~SeekRunnable() {
    qDebug("SeekRunnable: destructor invoked");
}

void SeekRunnable::run() {
    mediaSeeker->exec();
}

bool MetaResolver::isSupported(const KUrl &kurl) {
    if (kurl.isEmpty() || !kurl.isValid()) {
        return false;
    }

    KFileItem fileItem(KFileItem::Unknown, KFileItem::Unknown, kurl);
    if (!fileItem.isFile()) {
        return false;
    }
    if (!supportedMimes.contains(fileItem.mimetype(), Qt::CaseInsensitive)) {
        return false;
    }
    return true;
}

MetaResolver::MetaResolver() : supportedMimes(Phonon::BackendCapabilities::availableMimeTypes().filter("audio/", Qt::CaseInsensitive)), codec(QTextCodec::codecForName("CP1251")) {
}

Item* MetaResolver::createItem(const KUrl &url) {
    Item* item = new Item();
    item->setUrl(url);
    qDebug() << "MetaResolver: resolving " << url.path();
    TagLib::FileRef ref(QFile::encodeName(url.path()).constData(), true, TagLib::AudioProperties::Fast);
    if (ref.isNull()) {
        return item;
    }

    TagLib::Tag* tag = ref.tag();
    if (!tag->isEmpty()) {
        //item->setAlbum(tag->album().toCString(false));

        QString album = codec->toUnicode(tag->album().toCString());
        QString artist = codec->toUnicode(tag->artist().toCString());
        QString title = codec->toUnicode(tag->title().toCString());
        QString year;
        int yearInt = tag->year();
        if (yearInt > 0) {
            year = QString::number(yearInt);
        }

        //qDebug() << "MetaResolver: title before: " << title;
        // some songs may not contain any title at all, taking it from url:
        {
            if (title.isNull() || title.isEmpty()) {
                title = url.fileName();
                int extIndex = title.lastIndexOf('.');
                if (extIndex > 0) {
                    title.remove(extIndex, title.length() - extIndex);
                }
            }
        }

        // some songs may contain title in form Artist - Title, we should check and strip it out:
        {
            title.remove(artist + " - ", Qt::CaseInsensitive);
            title.remove(artist + ": ", Qt::CaseInsensitive);
        }
        //qDebug() << "MetaResolver: title after:" << title;

        item->setAlbum(album);
        item->setArtist(artist);
        item->setTitle(title);
        item->setYear(year);
    }

    int length = ref.audioProperties()->length();
    if (length > 0) {
        item->setDurationSeconds(length);
        time.setHMS(length / 3600, (length / 60) % 60, length % 60);
        if (time.hour() > 0) {
            item->setDuration(time.toString("HH:mm:ss"));
        }
        else {
            item->setDuration(time.toString("mm:ss"));
        }
    }

    //qDebug() << "MetaResolver: resolving complete.";

    return item;
}
